#include <map>
#include <iostream>
#include <sstream>
#include <unistd.h>

#include "parser.h"
#include "tty_io.hpp"
#include "helper.hpp"
#include "interface.hpp"

extern std::map<std::string, Base*> gids;
extern std::map<std::string, Mode> modes;
extern std::map<std::string, Panel> panels;
extern std::map<std::string, Sensor> sensors;
extern std::map<std::string, Command> commands;
extern std::map<std::string, Timer> user_timers;
extern std::map<std::string, Device> other_devices;
extern std::map<std::string, Serial_info> port_map;

/* GSM class implementation */
void Gsm::send_msg() 
{
    char *msg;
    char resp[256];
    std::string msg_num;

    msg_num = "AT+CMGS=\"" + this->number + "\"";

    msg = (char*)malloc(msg_num.size() + 2);
    strcpy(msg, msg_num.c_str());
    write_to_gsm(msg);
    free(msg);

    usleep(200000);
    read_from_gsm(resp);

    msg = (char*)malloc(this->text.size() + 2);
    strcpy(msg, this->text.c_str());
    logger("sending msg = %s\n", msg);

    for(size_t i = 0; i < strlen(msg); ++i){
        if(msg[i] == '^') {
            msg[i] = 10;
        }
    }

    write_to_gsm(msg);
    free(msg);
    usleep(300000);
    read_from_gsm(resp);

    char ctrl_z[4];
    ctrl_z[0] = 0x1A;
    ctrl_z[1] = 0;

    /* finish the msg */
    write_to_gsm(ctrl_z);
}

void Gsm::set(std::string v) 
{
    send_msg();
}

std::string Gsm::get() 
{
    return "gsm";
}

/* Base Class implementation */
void Base::set(std::string v)
{
    pthread_mutex_lock(&this->set_get_mutex);
    this->value = v;
    pthread_mutex_unlock(&this->set_get_mutex);
}

std::string Base::get()
{   
    std::string v;
    pthread_mutex_lock(&this->set_get_mutex);
    v = this->value;
    pthread_mutex_unlock(&this->set_get_mutex);
    return v;
}

/* Base Sensor implementation */
void Sensor::set(float v)
{
    pthread_mutex_lock(&this->set_get_mutex);
    this->s_value = v;
    pthread_mutex_unlock(&this->set_get_mutex);
}

/* Base Sensor implementation */
void Sensor::set(std::string v)
{
    pthread_mutex_lock(&this->set_get_mutex);
    this->s_value = atoi(v.c_str());
    pthread_mutex_unlock(&this->set_get_mutex);
}

std::string Sensor::get()
{
    std::string v;
    pthread_mutex_lock(&this->set_get_mutex);
    v = to_string(this->s_value);
    pthread_mutex_unlock(&this->set_get_mutex);
    return v;
}

/* Panel implementation */
Panel::Panel()
{
    this->sevp.sigev_notify = SIGEV_NONE;

    timer_create(CLOCK_REALTIME, &this->sevp, &this->timerid);
}

void Panel::start_timer()
{
    timer_settime(this->timerid, 0, &this->timeout, NULL);
}

void* Panel::update_routine(void* obj)
{   
    Sensor *s;
    Panel *p = (Panel*)obj;
    std::string s_gid;
    struct itimerspec curr_value;
    std::vector<uint8_t> resp;
    std::map<std::string, Sensor>::iterator b_it;

    p->start_timer();
    for(;;){
        timer_gettime(p->timerid, &curr_value);
        if((!curr_value.it_value.tv_sec && !curr_value.it_value.tv_nsec)){
            p->start_timer();
        } else {
            nanosleep(&curr_value.it_value, NULL);
            continue;
        }

        resp.clear();
        if(!read_from_device(p->port, p->update_data, resp)){
            logger("can't read from device, gid = [PANEL], data = [%s], device = [%s]\n",
                    p->update_data.c_str(), p->port.c_str());
            p->start_timer();
            continue;
        }
        if(resp.size() < 10){
            logger("Wrong reply from device, size. [%s]:[%d]\n",
                    __func__, __LINE__);
            print_resp(resp);
        }

        for(size_t i = 0; i < p->s_gids.size(); i++){
            b_it = sensors.find(p->s_gids[i]);
            if(b_it == sensors.end()){
                logger("wrong s_gid, [%s]:[%d]\n", __func__, __LINE__);
                continue;
            }

            s = &sensors[p->s_gids[i]];

            if(s->type == "contact"){
                uint16_t two_bytes = 0;
                two_bytes = (uint16_t)resp[4] << 8;
                two_bytes |= resp[5];
                if(0 == (two_bytes & (1 << (s->id - 1)))){
                    s->set(0);
                } else {
                    s->set(1);
                }
            } else if(s->type == "button"){
                uint32_t four_bytes = 0;
                uint32_t mode = -1;

                four_bytes = (uint32_t)resp[6] << 16;
                four_bytes |= (uint32_t)resp[7] << 8;
                four_bytes |= resp[8];

                mode = (four_bytes >> (2 * (s->id - 1))) & 3;
                switch(mode){
                    case 0:
                    case 1:
                    case 2:
                    case 3:
                        s->set(mode);
                        break;
                    default:
                        logger("wrong Button mode [%d]\n", mode);
                        break;
                }
            } else if(s->type == "digital"){
                for(size_t j = 9; j < resp.size() - 1; j += 3){
                    if(resp[j] == s->id){
                        uint16_t tmp = 0;
                        float f;

                        tmp = resp[j+1] << 8;
                        tmp |= resp[j+2];
                        f = tmp;
                        if(s->gid == "g43" && f == 65425){
                            logger("skipping brightness value.\n");
                            continue;
                        }
                        s->set(f/10);
                    }
                }
            } else {
                logger("wrong sensor type.\n");
                continue;
            }
           //logger("update sensor->%s:[%s]\n", s->gid.c_str(), s->get().c_str());
        }
    }
    pthread_exit(NULL);
}


void Panel::set(std::string v)
{
    logger("panel don't have any value to be set.\n");
}

std::string Panel::get()
{
    logger("Panel hasn't any value yet.\n");
    return "Error: panel hasn't any value."; 
}

void Panel::init()
{
    std::vector<uint8_t> resp;
    std::map<std::string, Sensor>::iterator b_it;

    if(!read_from_device(this->port, this->init_data, resp)){
        logger("can't read from device, gid = [%s], data = [%s], device = [%s]\n",
                this->gid.c_str(), this->init_data.c_str(), this->port.c_str());
        return;
    }

    //if(resp.size() < 20){
        //logger("read bytes count is less then 20, exiting\n");
    //}

    for(size_t i = 0; i < this->s_gids.size(); i++){
        b_it = sensors.find(this->s_gids[i]);
        if(b_it == sensors.end()){
            logger("Error: wrong sensor 'gid' in the panel\n");
            continue;
        }

        if(sensors[this->s_gids[i]].type == "contact"){
            uint16_t two_bytes = 0;
            two_bytes = (uint16_t)resp[4] << 8;
            two_bytes |= resp[5];
            if(0 == (two_bytes & ( 1 << (sensors[this->s_gids[i]].id - 1) ))){
                sensors[this->s_gids[i]].s_value = 0;
            } else {
                sensors[this->s_gids[i]].s_value = 1;
            }
        } else if(sensors[this->s_gids[i]].type == "button"){
            uint32_t four_bytes = 0;
            four_bytes = (uint32_t)resp[6] << 16;
            four_bytes |= (uint32_t)resp[7] << 8;
            four_bytes |= resp[8];
            switch((four_bytes >> (2 * sensors[this->s_gids[i]].id - 1)) & 3){
                case 0:
                    sensors[this->s_gids[i]].s_value = 0;
                    break;
                case 1:
                    sensors[this->s_gids[i]].s_value = 1;
                    break;
                case 2:
                    sensors[this->s_gids[i]].s_value = 2;
                    break;
                case 3:
                    sensors[this->s_gids[i]].s_value = 3;
                    break;
                default:
                    printf("Wrong Button mode.\n");
            }
        } else if(sensors[this->s_gids[i]].type == "digital"){
            for(size_t j = 9; j < resp.size() - 1; j += 3){
                if(resp[j] == sensors[this->s_gids[i]].id){
                    uint16_t tmp = 0;
                    float f;
                    tmp = resp[j+1] << 8;
                    tmp |= resp[j+2];
                    f = tmp;
                    sensors[this->s_gids[i]].s_value = f / 10;
                }
            }
        } else {
            logger("Error: wrong sensor type.\n");
        }
        logger("INIT SENSOR->%s:%02f\n", 
                this->s_gids[i].c_str(), 
                sensors[this->s_gids[i]].s_value);
    }
}

void Panel::update()
{
    pthread_create(&this->thread, NULL, &Panel::update_routine,
            static_cast<void*>(this));
}

void Panel::wait()
{
    pthread_join(this->thread, NULL);
}

/* Device Class implementation */
void Device::set(std::string v)
{
    pthread_mutex_lock(&this->set_get_mutex);
    std::vector<uint8_t> resp;
    std::string data = this->data_for_set;
    find_and_replace(data, "value", v);
    if(!read_from_device(this->port, data, resp)){
        logger("can't read from device, gid = [%s], data = [%s], device = [%s]\n",
                this->gid.c_str(), data.c_str(), this->port.c_str());
    }
    pthread_mutex_unlock(&this->set_get_mutex);
}

std::string Device::get()
{   
    pthread_mutex_lock(&this->set_get_mutex);
    std::vector<uint8_t> resp;
    float value = 0;

    if(!read_from_device(this->port, this->data_for_get, resp)){
        logger("can't read from device, gid = [%s], data = [%s], device = [%s]\n",
                this->gid.c_str(), this->data_for_get.c_str(), this->port.c_str());
        pthread_mutex_unlock(&this->set_get_mutex);
        return "-1";
    }

    if(this->start_byte > resp.size() - 2 || 
            this->byte_count > (resp.size() - this->start_byte - 1) ||
            this->byte_count == 0){
        logger("Wrong start_byte or byte_count specified, start_byte = %d, "
                "byte_count = %d\n", (int)(this->start_byte + 1), (int)this->byte_count);
        print_resp(resp);
        pthread_mutex_unlock(&this->set_get_mutex);
        return "-1";
    }

    for(size_t i = 0; i < this->byte_count; i++) {
        value += resp[this->start_byte + i];
    }

    pthread_mutex_unlock(&this->set_get_mutex);
    return to_string(value);
}

/* Command Class implementation */
void Command::set(std::string v)
{
    pthread_mutex_lock(&this->set_get_mutex);
    std::vector<uint8_t> resp;
    std::string data = this->data_for_set;
    find_and_replace(data, "value", v);
    if(read_from_device(this->port, data, resp)){
        this->value = v;
    } else { 
        logger("can't read from device, gid = [%s], data = [%s], device = [%s]\n",
                this->gid.c_str(), data.c_str(), this->port.c_str());
    }
    pthread_mutex_unlock(&this->set_get_mutex);
}

std::string Command::get()
{   
    std::string v;
    pthread_mutex_lock(&this->set_get_mutex);
    v = this->value;
    pthread_mutex_unlock(&this->set_get_mutex);
    return v;
}

void Command::init()
{   
    int byte_num = atoi(this->uniq.c_str());
    std::string current_value = "0";
    std::vector<uint8_t> resp;
    if(!read_from_device(this->port, this->data_for_get, resp)){
        logger("can't read from device, gid = [%s], data = [%s], device = [%s]\n",
                this->gid.c_str(), this->data_for_get.c_str(), this->port.c_str());
        return;
    }

    if(byte_num != -1){
        if(byte_num >= (int)resp.size()){
            logger("Wrong uniq number\n");
            this->value = "-1";
            return;
        }
        int a = resp[byte_num-1];
        current_value = to_string(a);
    } else {
        /* TODO: */
        this->value = "-1";
    }

    logger("INIT [%s]:%s\n", this->name.c_str(), current_value.c_str());
    this->value = current_value;
}

/* Condition class implementation */
Condition::Condition()
{
    /* init mutex */
    pthread_mutex_init(&this->mutex, NULL);

    /* sets the default timer to 0.0 sec for the checking the condition. */
    this->sevp.sigev_notify = SIGEV_NONE;
    this->step_sevp.sigev_notify = SIGEV_NONE;

    timer_create(CLOCK_REALTIME, &this->sevp, &this->timerid);
    timer_create(CLOCK_REALTIME, &this->step_sevp, &this->step_timerid);

    timeout.it_value.tv_sec = 0;
    timeout.it_value.tv_nsec = 0;
    timeout.it_interval.tv_sec = 0;
    timeout.it_interval.tv_nsec = 0;

    step_timeout.it_value.tv_sec = 0;
    step_timeout.it_value.tv_nsec = 0;
    step_timeout.it_interval.tv_sec = 0;
    step_timeout.it_interval.tv_nsec = 0;
}

void* Condition::start_routine(void* obj)
{
    Parser eval;
    int eval_result;
    Condition* c = (Condition*)obj;
    std::string eval_expr = c->expr;
    std::map<std::string, Base*>::iterator b_it;

    /* Extracting 'g*' global IDs from the expression */
    std::vector<std::string> gids_str;
    extract_gids(c->expr, gids_str);

    bool timeout_specified = false;
    bool step_timeout_specified = false;
    bool forever = true;

    if(c->step_timeout.it_value.tv_sec != 0 || c->step_timeout.it_value.tv_nsec != 0){
        step_timeout_specified = true;
        c->start_timer("step_timeout");
    }

    if(c->timeout.it_value.tv_sec || c->timeout.it_value.tv_nsec){
        timeout_specified = true;
        c->start_timer("timeout");
    }

    if(!timeout_specified && !step_timeout_specified){
        forever = false;
    }

    do {
        /* Reset the expression string and start over. */
        eval_expr = c->expr;

        if(step_timeout_specified){
            struct itimerspec curr_value;
            timer_gettime(c->step_timerid, &curr_value);

            if((!curr_value.it_value.tv_sec && !curr_value.it_value.tv_nsec)){
                c->start_timer("step_timeout");
            } else {
                nanosleep(&curr_value.it_value, NULL);
                continue;
            }
        }


        usleep(5);
        pthread_mutex_lock(&c->mutex);
        usleep(5);
        /* find and replace g* string with it's value. */
        for(std::vector<std::string>::iterator g_it = gids_str.begin(); 
                g_it < gids_str.end(); ++g_it){
            b_it = gids.find(*g_it);
            if(b_it != gids.end()){
                find_and_replace(eval_expr, *g_it, gids[*g_it]->get());
            } else {
                logger("wrong 'gid' in the expression\n");
                pthread_mutex_unlock(&c->m->working_mutex);
                continue;
            }
        }

        find_and_replace(eval_expr, "value", c->m->get_value());
        find_and_replace(eval_expr, "off", "0");
        find_and_replace(eval_expr, "on", "1");

        replace_timers(eval_expr);

        eval_result = eval.parse(eval_expr.c_str());
        //logger("GID[%s]#Eval %s: %s Result: %d\n", c->m->gid.c_str(),
        //c->expr.c_str(), eval_expr.c_str(), eval_result);

        if(eval_result == 1){
            if(timeout_specified){
                if(c->is_timeout(c->timerid)){
                    exec(c->true_cmds, *c);
                    c->start_timer("timeout");
                }
            } else {
                exec(c->true_cmds, *c);
            }
        } else {
            if(timeout_specified){
                /* reset_timeout */
                c->start_timer("timeout");
            }
            exec(c->false_cmds, *c);
        }
        pthread_mutex_unlock(&c->mutex);
        usleep(5);
    } 
    while(forever);

    pthread_mutex_lock(&c->m->working_mutex);
    int w_count = --c->m->working_count;
    pthread_mutex_unlock(&c->m->working_mutex);

    if(w_count == 0) {
        c->m->set("off");
    }

    pthread_exit(NULL);
}

void Condition::start()
{   
    pthread_create(&this->thread, NULL, &Condition::start_routine,
            static_cast<void*>(this));
}

void Condition::stop()
{   
    if(this->thread){
        pthread_cancel(this->thread);
    }
}

void Condition::wait()
{
    pthread_join(this->thread, NULL);
}

bool Condition::is_timeout(timer_t &timerid)
{
    struct itimerspec curr_value;

    timer_gettime(timerid, &curr_value);
    return (!curr_value.it_value.tv_sec && !curr_value.it_value.tv_nsec);
}

void Condition::start_timer(std::string t_str)
{
    if(t_str == "step_timeout"){
        timer_settime(this->step_timerid, 0, &this->step_timeout, NULL);
    } else if(t_str == "timeout"){
        timer_settime(this->timerid, 0, &this->timeout, NULL);
    }
}

/* Mode class implementation */
void Mode::start()
{   
    this->working_count = cnds.size();

    /* Executing 'actions'. */
    for(size_t i = 0; i < this->actions.size(); i++) {
        if(this->actions[i].first == this->gid){
            if(this->actions[i].second == "off"){
                this->state = "off";
                return;
            } else {
                std::string tmp = this->actions[i].second;
                if(tmp[0] == '('){
                    tmp = to_string(eval_expr(tmp, this->value));
                }
                this->set_value(tmp);
            }
        } else {
            std::string tmp = this->actions[i].second;
            if(tmp[0] == '('){
                tmp = to_string(eval_expr(tmp, this->value));
            }
            gids[this->actions[i].first]->set(tmp);
        }
    }

    /* Starting all conditions via threads. */
    for(std::vector<Condition>::iterator it = this->cnds.begin(); 
            it < this->cnds.end(); ++it){
        it->m = this;
        it->start();
    }
}

void Mode::stop()
{   
    Condition* c = NULL;

    /* Wait for all conditions to be finished. */
    for(std::vector<Condition>::iterator it = this->cnds.begin(); 
            it < this->cnds.end(); ++it){
        /* determine if the stop routine was called from the same thread. */
        if(pthread_equal(it->thread, pthread_self())){
            /* save the current condition for stopping it later. */
            c = &(*it);
        } else {
            pthread_mutex_lock(&it->mutex);
            it->stop();
            pthread_mutex_unlock(&it->mutex);
        }
    }

    if(c){
        pthread_mutex_unlock(&c->mutex);
        pthread_mutex_unlock(&this->set_get_mutex);
        pthread_exit(NULL);
    }
}

void Mode::set(std::string v)
{
    pthread_mutex_lock(&this->set_get_mutex);
    if(v != "off"){
        if(this->state == "off") {
            logger("Turning 'on' Mode: [%s]\n", this->gid.c_str());
            this->state = "on";
            if(v != "on"){
                this->set_value(v);
            }
            this->start();
        } else {
            if(this->get_value() != v){
                this->set_value(v);
                logger("the mode [%s] value is changed to '%s'\n",
                        this->gid.c_str(), v.c_str());
            }
        }
    } else {
        if(this->state != "off") {
            logger("Turning 'off' Mode: [%s]\n", this->gid.c_str());
            this->state = "off";
            this->stop();
        } else {
            logger("the mode [%s] is already turned 'off'\n",
                    this->gid.c_str());
        }
    }
    pthread_mutex_unlock(&this->set_get_mutex);
}

std::string Mode::get()
{
    std::string v;
    pthread_mutex_lock(&this->set_get_mutex);
    if(this->state == "on"){
        v = "1";
    } else {
        v = "0";
    }
    pthread_mutex_unlock(&this->set_get_mutex);

    return v;
}

void Mode::set_value(std::string v)
{
    pthread_mutex_lock(&this->v_mutex);
    this->value = v;
    pthread_mutex_unlock(&this->v_mutex);
}

std::string Mode::get_value()
{
    pthread_mutex_lock(&this->v_mutex);
    std::string v;
    v = this->value ;
    pthread_mutex_unlock(&this->v_mutex);
    return v;
}

/* Timer class implementation */
void* Timer::start_routine(void* obj)
{
    Timer* t = (Timer*)obj;
    double eval_result;

    t->start_timer();
    printf("### TIMER [%s] called with value = [%s]\n", t->gid.c_str(), t->value.c_str());
    while(true) {
        if(!(t->value == "0")){
            struct itimerspec curr_value;
            timer_gettime(t->timerid, &curr_value);

            if((!curr_value.it_value.tv_sec && !curr_value.it_value.tv_nsec)){
                t->start_timer();
            } else {
                nanosleep(&curr_value.it_value, NULL);
                continue;
            }
        }

        printf("### TIMER [%s] expired, calling actions\n", t->gid.c_str());
        pthread_mutex_lock(&t->t_mutex);
        std::vector< std::pair<std::string, std::string> > cmd = t->actions;
        for(size_t i = 0; i < cmd.size(); i++) {
            if(cmd[i].second[0] == '('){
                replace_timers(cmd[i].second);
                find_and_replace(cmd[i].second, "value", t->get_value());
                eval_result = eval_expr2(cmd[i].second);
                if(cmd[i].first == t->gid && cmd[i].second == "off"){
                    t->state = "off";
                    pthread_mutex_unlock(&t->t_mutex);
                    pthread_exit(NULL);
                }
                printf("cmd = %s, value = %f\n", cmd[i].first.c_str(), eval_result);
                gids[cmd[i].first]->set(to_string((int)eval_result));
            } else {
                if(cmd[i].first == t->gid && cmd[i].second == "off"){
                    t->state = "off";
                    pthread_mutex_unlock(&t->t_mutex);
                    pthread_exit(NULL);
                }
                gids[cmd[i].first]->set(cmd[i].second);
            }
        }
        if(!(t->value == "0")){
            printf("### NEW TIMER [%s] called with value = [%s]\n", t->gid.c_str(), t->value.c_str());
            t->start_timer();
        }
        pthread_mutex_unlock(&t->t_mutex);
    }

    pthread_exit(NULL);
}

void Timer::start()
{   
    pthread_create(&this->thread, NULL, &Timer::start_routine,
            static_cast<void*>(this));
}

void Timer::stop()
{   
    pthread_mutex_lock(&this->t_mutex);
    printf("off 1\n");
    if(this->thread){
    printf("off 2\n");
        pthread_cancel(this->thread);
    }
    pthread_mutex_unlock(&this->t_mutex);
}

void Timer::set(std::string v)
{
    pthread_mutex_lock(&this->set_get_mutex);
    if(v != "off"){
        if(this->state == "off") {
            logger("Turning 'on' Timer: [%s]\n", this->gid.c_str());
            this->state = "on";
            if(v != "on"){
                this->set_value(v);
            }
            this->start();
        } else {
            if(this->get_value() != v){
                this->set_value(v);
                logger("the timer's [%s] value is changed to '%s'\n",
                        this->gid.c_str(), v.c_str());
            }
        }
    } else {
        if(this->state != "off") {
            logger("Turning 'off' Timer: [%s]\n", this->gid.c_str());
            this->state = "off";
            this->stop();
        } else {
            logger("the timer [%s] is already turned 'off'\n",
                    this->gid.c_str());
        }
    }
    pthread_mutex_unlock(&this->set_get_mutex);
}

std::string Timer::get()
{
    std::string v;
    pthread_mutex_lock(&this->set_get_mutex);
    if(this->state == "on"){
        v = "1";
    } else {
        v = "0";
    }
    pthread_mutex_unlock(&this->set_get_mutex);

    return v;
}

void Timer::set_value(std::string v)
{
    pthread_mutex_lock(&this->v_mutex);
    this->value = v;
    float f = atof(v.c_str());
    float fractional;
    float integral;
    fractional = modff(f, &integral);
    this->timeout.it_value.tv_sec = (int)integral;
    this->timeout.it_value.tv_nsec = (long)lrintf((fractional) * 1000) * 1000000;
    this->timeout.it_interval.tv_sec = 0;
    this->timeout.it_interval.tv_nsec = 0;
    pthread_mutex_unlock(&this->v_mutex);
}

std::string Timer::get_value()
{
    std::string v;
    pthread_mutex_lock(&this->v_mutex);
    v = this->value ;
    pthread_mutex_unlock(&this->v_mutex);
    return v;
}

bool Timer::is_timeout()
{
    struct itimerspec curr_value;

    timer_gettime(this->timerid, &curr_value);
    return (!curr_value.it_value.tv_sec && !curr_value.it_value.tv_nsec);
}

void Timer::start_timer()
{
    timer_settime(this->timerid, 0, &this->timeout, NULL);
}
